#include <QtGui>

#include "HtmHighlighter.h"

HtmHighlighter::HtmHighlighter(QTextDocument *parent)
    : QSyntaxHighlighter(parent)
{
    HighlightingRule rule;
    QColor color;

    _config = new QSettings(QSettings::defaultFormat(), QSettings::UserScope,
                            qApp->organizationName(), "htm.syntax");

    //! numbers
    color = _config->value("number/color", QColor(Qt::darkBlue).name()).toString();
    _numberFormat.setForeground(color);
    rule.pattern = QRegExp("\\b[0-9]+\\b");
    rule.format = _numberFormat;
    _highlightingRules.append(rule);

    //! html tags
    color = _config->value("tag/color", QColor(Qt::darkBlue).name()).toString();
    _tagFormat.setForeground(color);
    QVariantList tags;
    tags << "!doctype" << "a" << "abbr" << "acronym" << "address" << "applet"
         << "area" << "b" << "base" << "basefont" << "bgsound" << "bdo" << "big"
         << "blink" << "blockquote" << "body" << "br" << "button" << "caption"
         << "center" << "cite" << "code" << "col" << "colgroup" << "comment"
         << "dd" << "del" << "dfn" << "dir" << "div" << "dl" << "dt" << "em"
         << "embed" << "fieldset" << "font" << "form" << "frame" << "frameset"
         << "h" << "h1" << "h2" << "h3" << "h4" << "h5" << "h6"
         << "head" << "hr" << "hta:application" << "html" << "i" << "iframe"
         << "img" << "input" << "ins" << "isindex" << "jsp:declaration"
         << "jsp:directive" << "jsp:expression" << "jsp:fallback"
         << "jsp:forward" << "jsp:getproperty" << "jsp:include" << "jsp:param"
         << "jsp:params" << "jsp:plugin" << "jsp:root" << "jsp:scriptlet"
         << "jsp:setproperty" << "jsp:usebean" << "kbd" << "label" << "legend"
         << "li" << "link" << "listing" << "map" << "marquee" << "menu"
         << "meta" << "multicol" << "nextid" << "nobr" << "noframes"
         << "noscript" << "object" << "ol" << "optgroup" << "option" << "p"
         << "param" << "plaintext" << "pre" << "q" << "s" << "samp" << "script"
         << "select" << "server" << "small" << "sound" << "spacer" << "span"
         << "strike" << "strong" << "style" << "sub" << "sup" << "table"
         << "tbody" << "td" << "textarea" << "textflow" << "tfoot" << "th"
         << "thead" << "title" << "tr" << "tt" << "u" << "ul" << "var"
         << "wbr" << "xmp" << "<" << ">" << "</" << " */>";
    _tags = _config->value("tag/list", tags).toList();
    foreach (const QVariant& tag, _tags) {
        rule.pattern = QRegExp(QString("\\b%1\\b").arg(tag.toString()));
        //! html is case insensitive
        rule.pattern.setCaseSensitivity(Qt::CaseInsensitive);
        rule.format = _tagFormat;
        _highlightingRules.append(rule);
    }

    //! html attributes
    color = _config->value("attribute/color", QColor(Qt::darkYellow).name()).toString();
    _attributeFormat.setForeground(color);
    QVariantList attributes;
    attributes << "abbr" << "accept-charset" << "accept" << "accesskey"
               << "action" << "align" << "alink" << "alt" << "applicationname"
               << "archive" << "autoFlush" << "axis" << "background"
               << "behavior" << "bgcolor" << "bgproperties" << "border"
               << "bordercolor" << "bordercolordark" << "bordercolorlight"
               << "borderstyle" << "buffer" << "caption" << "cellpadding"
               << "cellspacing" << "char" << "charoff" << "charset"
               << "checked" << "cite" << "class" << "classid" << "clear"
               << "code" << "codebase" << "codetype" << "color" << "cols"
               << "colspan" << "compact" << "content" << "contentType"
               << "coords" << "data" << "datetime" << "declare" << "defer"
               << "dir" << "direction" << "disabled" << "dynsrc" << "encoding"
               << "enctype" << "errorPage" << "extends" << "face" << "file"
               << "flush" << "for" << "frame" << "frameborder"
               << "framespacing" << "gutter" << "headers" << "height"
               << "href" << "hreflang" << "hspace" << "http-equiv" << "icon"
               << "id" << "import" << "info" << "isErrorPage" << "ismap"
               << "isThreadSafe" << "label" << "language" << "leftmargin"
               << "link" << "longdesc" << "loop" << "lowsrc" << "marginheight"
               << "marginwidth" << "maximizebutton" << "maxlength" << "media"
               << "method" << "methods" << "minimizebutton" << "multiple"
               << "name" << "nohref" << "noresize" << "noshade" << "nowrap"
               << "object" << "onabort" << "onblur" << "onchange" << "onclick"
               << "ondblclick" << "onerror" << "onfocus" << "onkeydown"
               << "onkeypress" << "onkeyup" << "onload" << "onmousedown"
               << "onmousemove" << "onmouseout" << "onmouseover" << "onmouseup"
               << "onreset" << "onselect" << "onsubmit" << "onunload" << "page"
               << "param" << "profile" << "prompt" << "property" << "readonly"
               << "rel" << "rev" << "rows" << "rowspan" << "rules" << "runat"
               << "scheme" << "scope" << "scrollamount" << "scrolldelay"
               << "scrolling" << "selected" << "session" << "shape"
               << "showintaskbar" << "singleinstance" << "size" << "span"
               << "src" << "standby" << "start" << "style" << "summary"
               << "sysmenu" << "tabindex" << "target" << "text" << "title"
               << "topmargin" << "type" << "urn" << "usemap" << "valign"
               << "value" << "valuetype" << "version" << "vlink" << "vrml"
               << "vspace" << "width" << "windowstate" << "wrap" << "xmlns:jsp";
    _attributes = _config->value("attribute/list", attributes).toList();
    foreach (const QVariant& attribute, _attributes) {
        rule.pattern = QRegExp(QString("\\b%1\\b").arg(attribute.toString()));
        //! html is case insensitive
        rule.pattern.setCaseSensitivity(Qt::CaseInsensitive);
        rule.format = _attributeFormat;
        _highlightingRules.append(rule);
    }

    //! special chars
    color = _config->value("special_char/color", QColor(Qt::darkCyan).name()).toString();
    _specialCharFormat.setForeground(color);
    QVariantList specialChars;
    specialChars << "&aacute;" << "&agrave;" << "&acirc;" << "&amp;"
                 << "&atilde;" << "&aring;" << "&auml;" << "&aelig;"
                 << "&ccedil;" << "&copy;" << "&eacute;" << "&egrave;"
                 << "&ecirc;" << "&euml;" << "&euro;" << "&iacute;"
                 << "&igrave;" << "&icirc;" << "&iuml;" << "&eth;"
                 << "&gt;" << "&lt;" << "&nbsp;" << "&ntilde;"
                 << "&oacute;" << "&ograve;" << "&ocirc;" << "&otilde;"
                 << "&ouml;" << "&oslash;" << "&quot;" << "&reg;"
                 << "&szlig;" << "&uacute;" << "&ugrave;" << "&ucirc;"
                 << "&uuml;" << "&yacute;" << "&thorn;" << "&trade;"
                 << "&yuml;" << "&#;";
    _specialChars = _config->value("special_char/list", specialChars).toList();
    foreach (const QVariant& specialChar, _specialChars) {
        rule.pattern = QRegExp(specialChar.toString());
        //! html is case insensitive
        rule.pattern.setCaseSensitivity(Qt::CaseInsensitive);
        rule.format = _specialCharFormat;
        _highlightingRules.append(rule);
    }

    //! quotation
    color = _config->value("quotation/color", QColor(Qt::darkGreen).name()).toString();
    _quotationFormat.setForeground(color);
    rule.pattern = QRegExp("(\".*\"|\'.*\')");
    rule.format = _quotationFormat;
    _highlightingRules.append(rule);

    /*!
     * multiline comment
     * \sa further process goes to highlightBlock()
     */
    color = _config->value("multiline_comment/color", QColor(Qt::darkGreen).name()).toString();
    _multiLineCommentFormat.setForeground(color);
    _commentStartExpression = QRegExp("<!--");
    _commentEndExpression = QRegExp("-->");

    saveSettings();
}

HtmHighlighter::~HtmHighlighter()
{
    delete _config;
}

void HtmHighlighter::saveSettings()
{
    _config->setValue("number/color", _numberFormat.foreground().color().name());
    _config->setValue("tag/color", _tagFormat.foreground().color().name());
    _config->setValue("tag/list", QVariant(_tags));
    _config->setValue("attribute/color", _attributeFormat.foreground().color().name());
    _config->setValue("attribute/list", QVariant(_attributes));
    _config->setValue("special_char/color", _specialCharFormat.foreground().color().name());
    _config->setValue("special_char/list", QVariant(_specialChars));
    _config->setValue("quotation/color", _quotationFormat.foreground().color().name());
    _config->setValue("multiline_comment/color", _multiLineCommentFormat.foreground().color().name());
    _config->sync();
}

void HtmHighlighter::highlightBlock(const QString &text)
{
    // singleline matches
    foreach (const HighlightingRule &rule, _highlightingRules) {
        QRegExp expression(rule.pattern);
        int index = expression.indexIn(text);
        while (index >= 0) {
            int length = expression.matchedLength();
            setFormat(index, length, rule.format);
            index = expression.indexIn(text, index + length);
        }
    }

    /* multiline matches
     * block-state:
     * 0 = comment-block-start-or-end, 1 = comment-block-not-end
     */
    setCurrentBlockState(0);

    int startIndex = 0;
    if (previousBlockState() != 1)
        startIndex = _commentStartExpression.indexIn(text);

    while (startIndex >= 0) {
        int endIndex = _commentEndExpression.indexIn(text, startIndex);
        int commentLength;
        if (endIndex == -1) {
            setCurrentBlockState(1);
            commentLength = text.length() - startIndex;
        } else {
            commentLength = endIndex - startIndex + _commentEndExpression.matchedLength();
        }
        setFormat(startIndex, commentLength, _multiLineCommentFormat);
        startIndex = _commentStartExpression.indexIn(text, startIndex + commentLength);
    }
}
